001 /* 002 * Copyright 2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.depot; 020 021 import java.io.IOException; 022 import java.rmi.server.RMIClassLoader; 023 import java.rmi.server.RMIClassLoaderSpi; 024 import java.net.MalformedURLException; 025 import java.net.URL; 026 import java.net.URLClassLoader; 027 import java.security.Permission; 028 import java.util.Collections; 029 import java.util.IdentityHashMap; 030 import java.util.Map; 031 032 import net.dpml.lang.StandardClassLoader; 033 034 /** 035 * The DepotRMIClassLoaderSpi handles the loading of classes that are based on 036 * plugin artifact types. 037 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 038 * @version 1.0.2 039 */ 040 public class DepotRMIClassLoaderSpi extends RMIClassLoaderSpi 041 { 042 private static final Map LOADERS = Collections.synchronizedMap( new IdentityHashMap( 5 ) ); 043 044 private RMIClassLoaderSpi m_delegate = RMIClassLoader.getDefaultProviderInstance(); 045 046 static 047 { 048 for( ClassLoader classloader = ClassLoader.getSystemClassLoader(); 049 classloader != null; classloader = classloader.getParent() ) 050 { 051 LOADERS.put( classloader, null ); 052 } 053 } 054 055 /** 056 * Default constructor. 057 */ 058 public DepotRMIClassLoaderSpi() 059 { 060 super(); 061 } 062 063 /** 064 * Provides the implementation for 065 * {@link RMIClassLoader#loadClass(URL,String)}, 066 * {@link RMIClassLoader#loadClass(String,String)}, and 067 * {@link RMIClassLoader#loadClass(String,String,ClassLoader)}. 068 * 069 * Loads a class from a codebase URL path, optionally using the 070 * supplied loader. 071 * 072 * @param codebase the list of URLs (separated by spaces) to load 073 * the class from, or <code>null</code> 074 * @param name the name of the class to load 075 * @param defaultLoader additional contextual class loader 076 * to use, or <code>null</code> 077 * @return the <code>Class</code> object representing the loaded class 078 * @exception MalformedURLException if <code>codebase</code> is 079 * non-<code>null</code> and contains an invalid URL, or 080 * if <code>codebase</code> is <code>null</code> and the system 081 * property <code>java.rmi.server.codebase</code> contains an 082 * invalid URL 083 * @exception ClassNotFoundException if a definition for the class 084 * could not be found at the specified location 085 */ 086 public Class loadClass( 087 String codebase, String name, ClassLoader defaultLoader ) 088 throws MalformedURLException, ClassNotFoundException 089 { 090 //if( null != codebase ) 091 //{ 092 // final String message = 093 // "Loading class: " 094 // + name 095 // + "\nCodebase: " 096 // + codebase; 097 // getLogger().debug( message ); 098 //} 099 return m_delegate.loadClass( codebase, name, defaultLoader ); 100 } 101 102 /** 103 * Provides the implementation for 104 * {@link RMIClassLoader#loadProxyClass(String,String[],ClassLoader)}. 105 * 106 * Loads a dynamic proxy class (see {@link java.lang.reflect.Proxy} 107 * that implements a set of interfaces with the given names 108 * from a codebase URL path, optionally using the supplied loader. 109 * 110 * <p>An implementation of this method must either return a proxy 111 * class that implements the named interfaces or throw an exception. 112 * 113 * @param codebase the list of URLs (space-separated) to load 114 * classes from, or <code>null</code> 115 * @param interfaces the names of the interfaces for the proxy class 116 * to implement 117 * @param defaultLoader additional contextual class loader 118 * to use, or <code>null</code> 119 * @return a dynamic proxy class that implements the named interfaces 120 * @exception MalformedURLException if <code>codebase</code> is 121 * non-<code>null</code> and contains an invalid URL, or 122 * if <code>codebase</code> is <code>null</code> and the system 123 * property <code>java.rmi.server.codebase</code> contains an 124 * invalid URL 125 * @exception ClassNotFoundException if a definition for one of 126 * the named interfaces could not be found at the specified location, 127 * or if creation of the dynamic proxy class failed (such as if 128 * {@link java.lang.reflect.Proxy#getProxyClass(ClassLoader,Class[])} 129 * would throw an <code>IllegalArgumentException</code> for the given 130 * interface list) 131 */ 132 public Class loadProxyClass( 133 String codebase, String[] interfaces, ClassLoader defaultLoader ) 134 throws MalformedURLException, ClassNotFoundException 135 { 136 //if( null != codebase ) 137 //{ 138 // getLogger().debug( "Loading proxy: " + codebase ); 139 //} 140 return m_delegate.loadProxyClass( codebase, interfaces, defaultLoader ); 141 } 142 143 /** 144 * Provides the implementation for 145 * {@link RMIClassLoader#getClassLoader(String)}. 146 * 147 * Returns a class loader that loads classes from the given codebase 148 * URL path. 149 * 150 * <p>If there is a security manger, its <code>checkPermission</code> 151 * method will be invoked with a 152 * <code>RuntimePermission("getClassLoader")</code> permission; 153 * this could result in a <code>SecurityException</code>. 154 * The implementation of this method may also perform further security 155 * checks to verify that the calling context has permission to connect 156 * to all of the URLs in the codebase URL path. 157 * 158 * @param codebase the list of URLs (space-separated) from which 159 * the returned class loader will load classes from, or <code>null</code> 160 * @return a class loader that loads classes from the given codebase URL 161 * path 162 * @exception MalformedURLException if <code>codebase</code> is 163 * non-<code>null</code> and contains an invalid URL, or 164 * if <code>codebase</code> is <code>null</code> and the system 165 * property <code>java.rmi.server.codebase</code> contains an 166 * invalid URL 167 * @exception SecurityException if there is a security manager and the 168 * invocation of its <code>checkPermission</code> method fails, or 169 * if the caller does not have permission to connect to all of the 170 * URLs in the codebase URL path 171 */ 172 public ClassLoader getClassLoader( String codebase ) 173 throws MalformedURLException, SecurityException 174 { 175 return m_delegate.getClassLoader( codebase ); 176 } 177 178 /** 179 * Provides the implementation for 180 * {@link RMIClassLoader#getClassAnnotation(Class)}. 181 * 182 * Returns the annotation string (representing a location for 183 * the class definition) that RMI will use to annotate the class 184 * descriptor when marshalling objects of the given class. 185 * 186 * @param cl the class to obtain the annotation for 187 * @return a string to be used to annotate the given class when 188 * it gets marshalled, or <code>null</code> 189 * @exception NullPointerException if <code>cl</code> is <code>null</code> 190 */ 191 public String getClassAnnotation( Class cl ) throws NullPointerException 192 { 193 final String annotations = getAnnotation( cl ); 194 //if( null != annotations ) 195 //{ 196 // System.out.println( "# " + cl.getName() + ", " + annotations ); 197 //} 198 return annotations; 199 } 200 201 private String getAnnotation( Class cl ) throws NullPointerException 202 { 203 String classname = cl.getName(); 204 int i = classname.length(); 205 if( ( i > 0 ) && classname.charAt( 0 ) == '[' ) 206 { 207 int j; 208 for( j=1; i > j && classname.charAt( j ) == '['; j++ ) 209 { 210 if( ( i > j ) && classname.charAt( j ) != 'L' ) 211 { 212 return null; 213 } 214 } 215 } 216 217 ClassLoader classloader = cl.getClassLoader(); 218 if( classloader == null || LOADERS.containsKey( classloader ) ) 219 { 220 return System.getProperty( "java.rmi.server.codebase" ); 221 } 222 223 String annotations = null; 224 if( classloader instanceof StandardClassLoader ) 225 { 226 annotations = ( (StandardClassLoader) classloader ).getAnnotations(); 227 } 228 else if( classloader instanceof URLClassLoader ) 229 { 230 annotations = getAnnotations( (URLClassLoader) classloader ); 231 } 232 233 if( annotations != null ) 234 { 235 return annotations; 236 } 237 else 238 { 239 return System.getProperty( "java.rmi.server.codebase" ); 240 } 241 } 242 243 private String getAnnotations( URLClassLoader classloader ) 244 { 245 StringBuffer buffer = new StringBuffer(); 246 return getAnnotations( buffer, classloader ); 247 } 248 249 private String getAnnotations( StringBuffer buffer, URLClassLoader classloader ) 250 { 251 packAnnotations( buffer, classloader ); 252 String result = buffer.toString(); 253 return result.trim(); 254 } 255 256 private void packAnnotations( StringBuffer buffer, URLClassLoader classloader ) 257 { 258 if( ClassLoader.getSystemClassLoader() == classloader ) 259 { 260 return; 261 } 262 263 ClassLoader parent = classloader.getParent(); 264 if( ( null != parent ) && ( parent instanceof URLClassLoader ) ) 265 { 266 packAnnotations( buffer, (URLClassLoader) parent ); 267 } 268 269 try 270 { 271 URL[] urls = classloader.getURLs(); 272 if( null != urls ) 273 { 274 SecurityManager manager = System.getSecurityManager(); 275 if( manager != null ) 276 { 277 for( int k = 0; k < urls.length; k++ ) 278 { 279 Permission permission = urls[k].openConnection().getPermission(); 280 if( permission != null ) 281 { 282 manager.checkPermission( permission ); 283 } 284 } 285 } 286 buffer.append( urlsToPath( urls ) + " " ); 287 } 288 } 289 catch( SecurityException e ) 290 { 291 boolean ignore = true; // ignore 292 } 293 catch( IOException e ) 294 { 295 boolean ignore = true; // ignore 296 } 297 } 298 299 private static String urlsToPath( URL[] urls ) 300 { 301 if( urls.length == 0 ) 302 { 303 return null; 304 } 305 else if( urls.length == 1 ) 306 { 307 final String path = urls[0].toExternalForm(); 308 if( !path.startsWith( "file:" ) ) 309 { 310 return path; 311 } 312 else 313 { 314 return ""; 315 } 316 } 317 StringBuffer buffer = new StringBuffer( urls[0].toExternalForm() ); 318 for( int i=1; i < urls.length; i++ ) 319 { 320 final String path = urls[i].toExternalForm(); 321 if( !path.startsWith( "file:" ) ) 322 { 323 buffer.append( ' ' ); 324 buffer.append( path ); 325 } 326 327 } 328 return buffer.toString(); 329 } 330 }